from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException, ElementClickInterceptedException
import sqlite3
import logging
import traceback

# 设置日志配置
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def setup_driver():
    options = webdriver.ChromeOptions()
    # options.add_argument('--headless')  # 运行无头浏览器
    options.add_argument('--disable-gpu')  # 禁用GPU加速
    driver = webdriver.Chrome(options=options)
    return driver

def create_tables(cursor):
    cursor.execute('''CREATE TABLE IF NOT EXISTS preaching (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        title TEXT UNIQUE,
                        organizer TEXT UNIQUE,
                        location TEXT,
                        time TEXT,
                        company_link TEXT UNIQUE)''')
    
    cursor.execute('''CREATE TABLE IF NOT EXISTS company (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        name TEXT UNIQUE,
                        location TEXT,
                        type TEXT,
                        industry TEXT,
                        size TEXT,
                        FOREIGN KEY (name) REFERENCES preaching(organizer) ON DELETE CASCADE
                        )''')
    
    cursor.execute('''CREATE TABLE IF NOT EXISTS position (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        title TEXT,
                        company_name TEXT,
                        salary TEXT,
                        work_location TEXT,
                        hiring_num TEXT,
                        major_requirements TEXT,
                        FOREIGN KEY (company_name) REFERENCES company(name) ON DELETE CASCADE)''')

def get_company_details(driver):
    try:
        wait = WebDriverWait(driver, 60)
        
        details_container = wait.until(EC.visibility_of_element_located(
            (By.CSS_SELECTOR, 'div.ant-space-horizontal.ant-space-align-center[style*="gap: 0px"]')
        ))
        
        detail_items = details_container.find_elements(By.CSS_SELECTOR, 'div.ant-space-item > span')
        details = [item.text.strip() for item in detail_items]
        
        return {
            "location": details[0] if len(details) > 0 else None,
            "type": details[1] if len(details) > 1 else None,
            "size": details[2] if len(details) > 2 else None,
            "industry": ';'.join(details[3:]) if len(details) > 3 else None  # 第三行之后的内容用 ";" 分隔
        }
    except Exception as e:
        logger.error(f"Error extracting company details: {e}")
        return {"type": None, "size": None, "industry": None}

def get_position_basic_info(position_card, wait):
    try:
        # 初始化默认值
        position_info = {
            "title": None,
            "salary": None,
            "work_location": None,
            "hiring_num": None
        }

        # 使用XPath定位包含所有子元素的父级容器
        container_element = WebDriverWait(position_card, 10).until(
            EC.presence_of_element_located((By.XPATH, './/div[contains(@class, "ant-space") and contains(@class, "ant-space-horizontal")]'))
        )
        
        # 获取整个容器的文本内容并去除前后空白字符
        all_text = container_element.text.strip()
        # 解析文本内容
        lines = all_text.split('\n')
        for line in lines:
            if "月" in line:
                position_info["salary"] = line.strip()
            elif "省" in line or "市" in line or "区" in line:  # 根据工作地点格式调整匹配条件
                position_info["work_location"] = line.strip()
            elif "人" in line and not position_info["hiring_num"]:
                position_info["hiring_num"] = line.strip()

        # 提取职位标题（在最外层 <a> 标签中）
        try:
            title_element = WebDriverWait(position_card, 10).until(
                EC.presence_of_element_located((By.XPATH, './/div[contains(@class, "ant-card-head-title")]/a'))
            )
            position_info["title"] = title_element.text.strip()
        except Exception as e:
            logger.error(f"Failed to find position title element: {e}")

        return position_info
    except Exception as e:
        logger.error(f"Error getting basic position info: {e}")
        return {}
    
def element_has_non_empty_text(locator):
    def _predicate(driver):
        try:
            element = driver.find_element(*locator)
            if element.is_displayed() and element.text.strip():
                return element
        except Exception:
            return False
    return _predicate

def get_major_requirements(driver, wait):
    try:
        # 定义XPath定位器
        locator = (By.XPATH, '//div[contains(@class, "ant-card-body")]')

        # 使用自定义等待条件等待直到元素可见并且包含非空文本
        ant_card_body = wait.until(element_has_non_empty_text(locator))
    
        card_body_text = ant_card_body.text.strip()
        
        lines = [line.strip() for line in card_body_text.split('\n') if line.strip()]
        major_requirements = '/'.join(lines)
        
        logger.info("Extracted major requirements: %s", major_requirements)
        return major_requirements
    except Exception as e:
        logger.error(f"Error extracting major requirements: {e}")
        logger.error(traceback.format_exc())
        return None

def process_new_window(driver, wait, position_info, company_name, cursor, conn):
    try:
        original_window = driver.current_window_handle
        all_windows_before_click = set(driver.window_handles)
        
        # 点击“查看详情”按钮后等待直到新的窗口数量为3
        view_detail_button = wait.until(EC.element_to_be_clickable((By.XPATH, './/button[contains(.,"查看详情")]')))
        driver.execute_script("arguments[0].scrollIntoView(true);", view_detail_button)
        driver.execute_script("window.scrollBy(0, -150);")
        driver.execute_script("arguments[0].click();", view_detail_button)

        # 等待直到新的窗口数量为3
        wait.until(lambda d: len(d.window_handles) == 3)
        
        all_windows_after_click = set(driver.window_handles)
        new_window = list(all_windows_after_click - all_windows_before_click)[0]  # 找到新打开的窗口
        
        # 切换到新的窗口
        driver.switch_to.window(new_window)

        # 确保页面加载完成并且目标元素存在
        wait.until(EC.presence_of_element_located((By.XPATH, '//div[contains(@class, "ant-card-body")]')))
        wait.until(EC.visibility_of_element_located((By.XPATH, '//div[contains(@class, "ant-card-body")]')))

        # 提取专业要求
        major_requirements = get_major_requirements(driver, wait)
        # 查询公司位置
        cursor.execute('SELECT location FROM company WHERE name = ?', (company_name,))
        company_location = cursor.fetchone()
        if company_location:
            company_location = company_location[0]
        else:
            company_location = None

        # 如果工作地点为空，则使用公司位置填充
        work_location = position_info['work_location'] or company_location

        # 插入职位信息到数据库
        cursor.execute('INSERT INTO position (title, company_name, salary, work_location, hiring_num, major_requirements) VALUES (?, ?, ?, ?, ?, ?)',
                       (position_info['title'], company_name, position_info['salary'], position_info['work_location'], position_info['hiring_num'], major_requirements))
        conn.commit()

        logger.info(f"Inserted position '{position_info['title']}' for {company_name} into the database")

    except Exception as e:
        logger.error(f"Error processing new window: {e}")
        logger.error(traceback.format_exc())
    finally:
        # 关闭最新打开的窗口并返回到之前的窗口
        if len(driver.window_handles) > 2:
            driver.close()
            driver.switch_to.window(list(all_windows_after_click - {new_window})[0])

def get_position_details(position_card, company_name, cursor, conn, driver):
    try:
        wait = WebDriverWait(driver, 60)

        # 获取职位基本信息
        position_info = get_position_basic_info(position_card, wait)

        # 处理新窗口的内容
        process_new_window(driver, wait, position_info, company_name, cursor, conn)

        logger.info(f"Processed position '{position_info['title']}' for {company_name}")

    except Exception as e:
        logger.error(f"Error processing position details: {e}")
        logger.error(traceback.format_exc())

def process_company_page(company_link, company_name, cursor, conn, driver):
    try:
        logger.info(f"Processing company page for {company_name} at {company_link}")
        
        # 打开新标签页并切换到它
        driver.execute_script("window.open('');")
        driver.switch_to.window(driver.window_handles[-1])
        
        # 加载公司页面
        driver.get(company_link)
        
        wait = WebDriverWait(driver, 60)
        # 确保页面加载完成
        try:
            wait.until(EC.presence_of_element_located((By.XPATH, '//div[contains(@class, "ant-space-item") and div[contains(@class, "ant-space")]]')))
            logger.info(f"Company page loaded successfully for {company_name}")
        except TimeoutException as te:
            logger.error(f"Timeout loading company page for {company_name}: {te}")
            driver.close()
            driver.switch_to.window(driver.window_handles[0])
            return
        
        # 获取公司详情信息
        company_info = get_company_details(driver)
        cursor.execute('INSERT OR IGNORE INTO company (name, location, type, industry, size) VALUES (?, ?, ?, ?, ?)',
                       (company_name, company_info['location'], company_info['type'], company_info['industry'], company_info['size']))
        conn.commit()
        logger.info(f"Inserted or ignored company details for {company_name}")

        # 确保“在招职位”选项卡可见并可点击
        job_listings_tab = wait.until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, 'div[data-node-key="2"] .ant-tabs-tab-btn'))
        )
        if not job_listings_tab.get_attribute("aria-selected") == "true":
            job_listings_tab.click()

        # 检查是否存在“登录查看更多职位”按钮
        try:
            wait = WebDriverWait(driver, 2)
            login_button = wait.until(
                EC.visibility_of_element_located((By.XPATH, '//button/span[text()="登录查看更多职位"]'))
            )
            logger.warning(f"Found '登录查看更多职位' button for {company_name}. Closing page and returning.")
            
            # 关闭当前标签页并返回到原始标签页
            driver.close()
            driver.switch_to.window(driver.window_handles[0])
            
            # 确保我们回到了原始页面
            original_url = 'https://jiuye.hrbust.edu.cn/preaching/index.jhtml?careerTalkBelongs=1'
            try:
                wait.until(EC.url_to_be(original_url))
                logger.info(f"Successfully returned to the original page: {original_url}")
            except TimeoutException as te:
                logger.error(f"Timeout waiting for original page URL: {te}")
                # 如果无法返回原页面，则重新加载
                driver.get(original_url)
                wait.until(EC.url_to_be(original_url))
                logger.info(f"Reloaded original page: {original_url}")
                
            return  # 直接返回，不再继续处理该公司的其他逻辑
            
        except TimeoutException:
            logger.info(f"No '登录查看更多职位' button found for {company_name}, continuing to process positions.")

        # 等待职位卡片加载完成
        try:
            positions = wait.until(
                EC.presence_of_all_elements_located((By.CSS_SELECTOR, '.ant-list-item.ant-list-item-no-flex .ant-card'))
            )
            logger.info(f"Found {len(positions)} positions for {company_name}")
            for position in positions:
                get_position_details(position, company_name, cursor, conn, driver)
        except TimeoutException as te:
            logger.error(f"Timeout waiting for positions to load for {company_name}: {te}")

        # 关闭当前标签页并返回到原始标签页
        driver.close()
        driver.switch_to.window(driver.window_handles[0])

        # 确保我们回到了原始页面
        original_url = 'https://jiuye.hrbust.edu.cn/preaching/index.jhtml?careerTalkBelongs=1'
        try:
            wait.until(EC.url_to_be(original_url))
            logger.info(f"Successfully returned to the original page: {original_url}")
        except TimeoutException as te:
            logger.error(f"Timeout waiting for original page URL: {te}")
            # 如果无法返回原页面，则重新加载
            driver.get(original_url)
            wait.until(EC.url_to_be(original_url))
            logger.info(f"Reloaded original page: {original_url}")

    except Exception as e:
        logger.error(f"Error processing company page {company_link}: {e}")
        logger.error(traceback.format_exc())
        # 确保即使出错也能关闭当前标签页并返回主页面
        if len(driver.window_handles) > 1:
            driver.close()
            driver.switch_to.window(driver.window_handles[0])

def process_main_page_items(driver, cursor, conn, wait):
    try:
        items = driver.find_elements(By.CLASS_NAME, 'item')
        logger.info(f"Processing {len(items)} main page items")
        
        # 开始一个事务
        cursor.execute('BEGIN TRANSACTION;')
        processed_count = 0  # 初始化计数器
        for item in items:
            try:
                title = item.find_element(By.XPATH, './/div[@class="ic-title"]/a').text.strip()
                organizer = item.find_element(By.XPATH, './/div[@class="ic-sub"]/span/a').get_attribute('title').strip()
                location = item.find_element(By.XPATH, './/div[@class="ic-sub"]/span[@class="m-l-20"]').text.replace("地点：", "").strip()
                time = item.find_element(By.XPATH, './/div[@class="i-right"]/div[@class="ir-time"]').text.strip()
                company_link = item.find_element(By.XPATH, './/div[@class="ic-sub"]/span/a').get_attribute('href')

                cursor.execute('INSERT INTO preaching (title, organizer, location, time, company_link) VALUES (?, ?, ?, ?, ?)',
                               (title, organizer, location, time, company_link))
                
                process_company_page(company_link, organizer, cursor, conn, driver)
                processed_count += 1
            except Exception as e:
                logger.error(f"Error processing main page item: {e}")
                logger.error(traceback.format_exc())
                continue  # 继续处理下一个项目
        
        # 提交所有更改
        conn.commit()
        logger.info("All main page items processed and committed.")
        return processed_count
    
    except Exception as e:
        logger.error(f"Critical error processing main page items: {e}")
        conn.rollback()  # 发生错误时回滚事务
        raise
    
    finally:
        # 确保只保留原始窗口
        if len(driver.window_handles) > 1:
            for handle in driver.window_handles[1:]:
                driver.switch_to.window(handle)
                driver.close()
            driver.switch_to.window(driver.window_handles[0])

def navigate_to_next_page(driver, wait):
    try:
        next_page_button = wait.until(EC.element_to_be_clickable((By.XPATH, '//div[contains(@class, "ib-sub") and contains(@class, "next")]')))
        if not next_page_button.is_enabled() or not next_page_button.is_displayed():
            logger.info("Next page button is not enabled or displayed, stopping pagination.")
            return False
        
        driver.execute_script("arguments[0].scrollIntoView(true);", next_page_button)
        driver.execute_script("window.scrollBy(0, -150);")
        driver.execute_script("arguments[0].click();", next_page_button)
        
        wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'item')))
        logger.info("Navigated to the next page successfully.")
        return True
    
    except TimeoutException:
        logger.info("Reached the last page or unable to find the next page button.")
        return False
    
    except Exception as e:
        logger.error(f"Error navigating to the next page: {e}")
        return False

def main():
    try:
        with setup_driver() as driver, sqlite3.connect('job_fair12.db') as conn:
            cursor = conn.cursor()
            create_tables(cursor)

            base_url = 'https://jiuye.hrbust.edu.cn/preaching/index.jhtml?careerTalkBelongs=1'
            driver.get(base_url)
            wait = WebDriverWait(driver, 60)

            count = 0
            max_records = 100
            
            while count <= max_records:
                try:
                    processed_count = process_main_page_items(driver, cursor, conn, wait)
                    if processed_count is None:
                        processed_count = 0
                    
                    count += processed_count
                    
                    if count >= max_records or not navigate_to_next_page(driver, wait):
                        break

                    # 确保新页面加载完成后才继续
                    wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'item')))

                except Exception as e:
                    logger.error(f"Main loop error: {e}")
                    raise  # 抛出异常以终止程序，避免隐藏问题

    except Exception as e:
        logger.critical(f"Critical error occurred: {e}")
        raise  # 抛出异常以终止程序，避免隐藏问题

if __name__ == "__main__":
    main()
